import { ELoading } from ":/const/status";
import { Action, Reducer } from "redux";
import { Epic, ofType } from "redux-observable";
import { mergeMap } from "rxjs";

/**
 * Action 接口
 */
export interface IAction<T = unknown> extends Action {
  // Action Type
  type: string;
  // 数据
  payload?: T;
}

/**
 * 通用的 State 类型
 */
export interface ICommState<T = {}> {
  // 加载状态
  loading?: ELoading;
  // 数据
  data?: T;
}

/**
 * 创建一个 Action Map
 * @param namespace 命名空间
 * @returns Action Map
 */
export function createActionType(namespace: string) {
  return [
    ELoading.IDLE,
    ELoading.LOADING,
    ELoading.LOADED,
    ELoading.ERROR,
    'setState'
  ].map(
    item => `fetch/${namespace}/${item}`);
}

/**
 * 创建 Selice 的参数
 */
export interface ICreateSeliceParams<T> {
  // 命名空间
  namespace: string;
  // 处理方法
  handler: (action: unknown) => T | Promise<T>;
  // 初始状态
  initState?: ICommState<T>;
}

/**
 * Selice 对象
 */
export interface ISelice<DataType> {
  // reducer
  reducer: { [key: string]: Reducer };
  // epic
  epic: Epic;
  // actions
  actions: {
    toDoActionCreater: <P = unknown>(payload?: P) => Action;
  },
  // selectors
  selectors: {
    selectState: (state: { [key: string]: ICommState<DataType> }) => ICommState<DataType>;
    selectData: (state: { [key: string]: ICommState<DataType> }) => DataType | undefined;
    selectLoading: (state: { [key: string]: ICommState<DataType> }) => ELoading | undefined;
  };
  // setState
  setState: (state: ICommState<DataType>) => void;
}

/**
 * 快速创建一个 Epic Selice
 * @param 创建 Selece 的参数
 * @returns Selice
 */
export function createSelice<T extends Object = {}>({
  namespace,
  handler,
  initState,
}: ICreateSeliceParams<T>): ISelice<T> {
  const [idleCase, startCase, okCase, errorCase, setStateCase] = createActionType(namespace);
  return {
    reducer: {
      [namespace]: (
        state: ICommState<T> = initState || {},
        action: IAction
      ): ICommState => {
        switch (action.type) {
          case idleCase:
          case startCase:
            return {
              ...state,
              loading: ELoading.LOADING
            }
          case okCase:
            return {
              ...state,
              data: action.payload,
              loading: ELoading.LOADED
            };
          case errorCase:
            return {
              ...state,
              data: undefined,
              loading: ELoading.ERROR
            };
          case setStateCase:
            return {
              ...state,
              ...action.payload as ICommState<T>,
            }
          default:
            return { ...state };
        }
      }
    },
    epic: (action$) => action$.pipe(
      ofType(startCase),
      mergeMap(async (action: IAction) => {
        try {
          // 去做异步请求
          const res = await handler(action.payload);
          return { type: okCase, payload: res };
        } catch (err) {
          console.error(err);
          // 请求失败触发失败的动作
          return { type: errorCase };
        }
      })
    ),
    actions: {
      toDoActionCreater: (payload) => ({ type: startCase, payload }),
    },
    selectors: {
      selectState: state => state[namespace],
      selectData: state => state[namespace]?.data,
      selectLoading: state => state[namespace]?.loading,
    },
    setState: (paylod) => ({ type: setStateCase, paylod }),
  }
}
